/**
 * Copyright (c) 2009 blurredVision
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "StdAfx.h"
#include "MeshGeometry.h"
#include "core/core.h"

namespace directx {

/** User defined vertex format! */
const D3DVERTEXELEMENT9 MESHVERTEX_DECLARATION[] = 
{
	{ 0, 0  * sizeof(float), D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },  // pos
	{ 0, 3  * sizeof(float), D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0 },  // normal
	{ 0, 6  * sizeof(float), D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT,  0 },  // tangent
	{ 0, 9  * sizeof(float), D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BINORMAL, 0 },  // binormal
	{ 0, 12  * sizeof(float), D3DDECLTYPE_FLOAT2,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },  // tex0
	D3DDECL_END()
};

MeshGeometry::MeshGeometry(void)
:	m_pMeshVertexDeclaration(NULL),
	m_pPMesh(NULL),
	m_pMesh(NULL)
{
}

MeshGeometry::~MeshGeometry(void)
{
	release();
}

void MeshGeometry::release()
{
	RELEASE( m_pMeshVertexDeclaration );
	RELEASE( m_pMesh);
	RELEASE( m_pPMesh);
}

void MeshGeometry::reset()
{
	ID3DXMesh*   tempMesh;

	pd3dDevice->CreateVertexDeclaration( MESHVERTEX_DECLARATION, &m_pMeshVertexDeclaration);

	// Temp vertex declaration
	D3DVERTEXELEMENT9 VERTEX_DECLARATION[] = 
	{
		{ 0, 0,  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},  // pos
		{ 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0},	// normal
		{ 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},	// texcoord blend
		D3DDECL_END()
	};

	if (FAILED( D3DXCreateMesh(m_uiNumFaces, m_uiNumVertices, D3DXMESH_MANAGED, VERTEX_DECLARATION, pd3dDevice, &tempMesh) )) {
		coreGetLog()->WriteError("Cannot create mesh. Failing to load geometry for mesh geometry", __FILE__, __LINE__);
		RELEASE( m_pMeshVertexDeclaration );
		return;
	};
	model::sMeshVertex* v = 0;
	tempMesh->LockVertexBuffer(0, (void**)&v);
	memcpy(v, m_pVertices, sizeof(model::sMeshVertex) * m_uiNumVertices);
	tempMesh->UnlockVertexBuffer();

	WORD *index = 0;
	tempMesh->LockIndexBuffer(0, (void**)&index);
	for (unsigned int i=0; i<m_uiNumVertices; ++i) {
		index[i] = i;
	}
	tempMesh->UnlockIndexBuffer();

	// Clone the mesh into the right vertex declaration format
	tempMesh->CloneMesh(D3DXMESH_MANAGED, MESHVERTEX_DECLARATION, pd3dDevice, &m_pMesh);

	RELEASE(tempMesh);

	DWORD* adjacencyInfo = new DWORD[m_pMesh->GetNumFaces() * 3];
	m_pMesh->GenerateAdjacency(0.001f, adjacencyInfo);

	DWORD* optimizedAdjacencyInfo = new DWORD[m_pMesh->GetNumFaces() * 3];
	m_pMesh->OptimizeInplace(D3DXMESHOPT_COMPACT | D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_VERTEXCACHE, adjacencyInfo, optimizedAdjacencyInfo, 0, 0);

	DELETE_ARRAY( adjacencyInfo);

	D3DXComputeTangentFrameEx(m_pMesh, 
								D3DDECLUSAGE_TEXCOORD, 0,   
								D3DDECLUSAGE_BINORMAL, 0, 
								D3DDECLUSAGE_TANGENT, 0, 
								D3DDECLUSAGE_NORMAL, 0, 
								D3DXTANGENT_WRAP_UV | D3DXTANGENT_GENERATE_IN_PLACE,
								optimizedAdjacencyInfo, 
								0.01f, 0.25f, 0.01f, 
								NULL,				// INplace, so output is null
								NULL);

	// Perform simple cleansing operations on mesh
    LPD3DXMESH pTempMesh;
    if( FAILED( D3DXCleanMesh( D3DXCLEAN_SIMPLIFICATION, m_pMesh, optimizedAdjacencyInfo, &pTempMesh, 
									optimizedAdjacencyInfo, NULL ) ) )
	{
		coreGetLog()->WriteError("Could not clean mesh for simplification. Failing to load geometry for mesh geometry", __FILE__, __LINE__);
		RELEASE( pTempMesh);
		return;
	}
	RELEASE( m_pMesh );
	m_pMesh = pTempMesh;

    // Perform a weld to try and remove excess vertices.
    // Weld the mesh using all epsilons of 0.0f.  A small epsilon like 1e-6 works well too
    D3DXWELDEPSILONS Epsilons;
    ZeroMemory( &Epsilons, sizeof(D3DXWELDEPSILONS) );
    if( FAILED( D3DXWeldVertices( m_pMesh, 0, &Epsilons,
                                       optimizedAdjacencyInfo,
                                       optimizedAdjacencyInfo, NULL, NULL ) ) )
    {
		coreGetLog()->WriteError("Could not weld vertices from adjacency info. Failing to load geometry for mesh geometry", __FILE__, __LINE__);
		return;
    }

    // Verify validity of mesh for simplification
    if( FAILED( D3DXValidMesh( m_pMesh, optimizedAdjacencyInfo, NULL ) ) )
    {
		coreGetLog()->WriteError("Mesh is not valid anymore. Failing to load geometry for mesh geometry", __FILE__, __LINE__);
		return;
    }

	// Generate LOD meshes
	if (FAILED( D3DXGeneratePMesh( m_pMesh, optimizedAdjacencyInfo, NULL, NULL, 0, D3DXMESHSIMP_FACE, &m_pPMesh ) ))
	{
		coreGetLog()->WriteError("Cannot create progressive mesh. Failing to load geometry for mesh geometry", __FILE__, __LINE__);
		return;
	}

	DWORD cVerticesMin = m_pPMesh->GetMinVertices();
    DWORD cVerticesMax = m_pPMesh->GetMaxVertices();

    DWORD cVerticesPerMesh = ( cVerticesMax - cVerticesMin + 10 ) / 10;

	m_pPMesh->SetNumVertices( cVerticesMax );

	DELETE_ARRAY(optimizedAdjacencyInfo);
}

bool MeshGeometry::initMesh()
{
	reset(); 
	return true;
}

void MeshGeometry::draw(unsigned int lodLevel)
{
	if (lodLevel != lastLOD) {
		m_pPMesh->SetNumVertices(m_uiNumLODVertices[lodLevel]);
		lastLOD = lodLevel;
	}
	// Draw the mesh
	m_pPMesh->DrawSubset(0);
}


} // namespace
